
// Copyright (c) WanSheng Intelligent Corp. All rights reserved.
// Licensed under the LGPL v3.0 license

#include "s7-plugin.h"

static int get_area_name(const char *area)
{
    if (strcmp(area, "db") == 0)
        return daveDB;
    else if (strcmp(area, "flags") == 0)
        return daveFlags;
    else if (strcmp(area, "inputs") == 0)
        return daveInputs;
    else if (strcmp(area, "outputs") == 0)
        return daveOutputs;
    else if (strcmp(area, "timer") == 0)
        return daveTimer;
    else if (strcmp(area, "counter") == 0)
        return daveCounter;
    else if (strcmp(area, "p") == 0)
        return daveP;
    else if (strcmp(area, "sysinfo") == 0)
        return daveSysInfo;
    else if (strcmp(area, "sysflag") == 0)
        return daveSysFlags;
    else if (strcmp(area, "ai") == 0)
        return daveAnaIn;
    else if (strcmp(area, "ao") == 0)
        return daveAnaOut;
    else
        return -1;
}

typedef enum
{
    T_Invalid_DataType = -1,
    T_Bits = 0,
    T_Bytes,
    T_S8int,
    T_Words,
    T_S16Int,
    T_DWords,
    T_S32Int,
    T_Float,
    T_DataType_Max
} s7_dt_t;

char *GZ_DataType[] =
    {
        "bits",
        "bytes",
        "s8int",
        "words",
        "s16int",
        "dwords",
        "s32int",
        "float"};

static int get_data_type_size(s7_dt_t dt)
{
    switch (dt)
    {
    case T_Bits:
        return 1;
    case T_Bytes:
        return 1;
    case T_S8int:
        return 1;
    case T_Words:
        return 2;
    case T_S16Int:
        return 2;
    case T_DWords:
        return 4;
    case T_Float:
        return 4;
    default:
        return 0;
    }
}

static char *parse_s7_cmd_query(char *query, int *AREA, int *area_Number, int *start_address, int *items, int *type)
{
    char buf[128] = {0};
    char di[128] = {0};
    int len = strlen(query);
    if (!find_key_value(query, len, "DI", di, sizeof(di), '&'))
        return NULL;

    if (!find_key_value(query, len, "area", buf, sizeof(buf), '&'))
        return NULL;
    *AREA = get_area_name(buf);
    if (*AREA == -1)
        return NULL;

    if (!find_key_value(query, len, "addr", buf, sizeof(buf), '&'))
        return NULL;
    *start_address = atoi(buf);

    *area_Number = 0;
    if (find_key_value(query, len, "an", buf, sizeof(buf), '&'))
        *area_Number = atoi(buf);

    if (items)
    {
        if (find_key_value(query, len, "ITEMS", buf, sizeof(buf), '&'))
            *items = atoi(buf);
        else
            *items = 1;
    }

    if (!find_key_value(query, len, "type", buf, sizeof(buf), '&'))
        return NULL;
    int i;
    for (i = 0; i < T_DataType_Max; i++)
    {
        if (strcmp(buf, GZ_DataType[i]) == 0)
        {
            *type = i;
            break;
        }
    }
    if (i == T_DataType_Max)
        return NULL;

    return strdup(di);
}

void res_s7_cmd_get_handler(restful_request_t *request, REQ_ENV_HANDLE req_env)
{
    cJSON *device_data;
    const char *payload;
    int res;

    if (request->query == NULL)
    {
        return;
    }
    int AREA, area_Number, start_address, items = 1;
    int type = T_Invalid_DataType;
    int length;
    s7_device_t *device;
    char *di = parse_s7_cmd_query(request->query, &AREA, &area_Number, &start_address, &items, &type);
    if (di == NULL)
        goto end;
    length = get_data_type_size((s7_dt_t)type) * items;

    device = find_device_open(di);
    if (device == NULL)
    {
        goto end;
    }

    if (AREA == daveDB || AREA == daveInputs || AREA == daveOutputs)
    {
        if (type == T_Bits)
        {
            res = daveReadBits(device->dc, AREA, area_Number, start_address, length, NULL);
        }
        else if (type != T_Invalid_DataType)
        {
            res = daveReadBytes(device->dc, AREA, area_Number, start_address, length, NULL);
        }
        else
        {
            fprintf(stderr, "[s7-plugin]No this type!\n");
            res = -1;
        }
    }
    else
    {
        res = daveReadBytes(device->dc, AREA, area_Number, start_address, length, NULL);
    }

    if (res != 0)
    {
        goto end;
        ;
    }

    device_data = cJSON_CreateArray();
    switch (AREA)
    {
    case daveOutputs:
        if (type == T_Bits || type == T_Bytes)
        {
            for (int b = 0; b < device->dc->AnswLen; b++)
            {
                unsigned int c = daveGetU8(device->dc);
                cJSON_AddItemToArray(device_data, cJSON_CreateNumber(c));
            }
        }
        else if (type == T_S8int)
        {
            for (int b = 0; b < device->dc->AnswLen; b++)
            {
                int c = daveGetS8(device->dc);
                cJSON_AddItemToArray(device_data, cJSON_CreateNumber(c));
            }
        }
        else if (type == T_Words)
        {
            for (int b = 0; b < items; b++)
            {
                unsigned int c = daveGetU16(device->dc);
                cJSON_AddItemToArray(device_data, cJSON_CreateNumber(c));
            }
        }
        else if (type == T_DWords)
        {
            for (int b = 0; b < items; b++)
            {
                unsigned int c = daveGetU32(device->dc);
                cJSON_AddItemToArray(device_data, cJSON_CreateNumber(c));
            }
        }
        else if (type == T_S16Int)
        {
            for (int b = 0; b < items; b++)
            {
                int c = daveGetS16(device->dc);
                cJSON_AddItemToArray(device_data, cJSON_CreateNumber(c));
            }
        }
        else if (type == T_S32Int)
        {
            for (int b = 0; b < items; b++)
            {
                int c = daveGetS32(device->dc);
                cJSON_AddItemToArray(device_data, cJSON_CreateNumber(c));
            }
        }
        else if (type == T_Float)
        {
            for (int b = 0; b < items; b++)
            {
                float c = daveGetFloat(device->dc);
                cJSON_AddItemToArray(device_data, cJSON_CreateNumber(c));
            }
        }
        break;
        // todo:other area like daveOutputs
    }

    payload = (const char *)cJSON_PrintUnformatted(device_data);
    wa_req_env_handover_payload(req_env, CONTENT_2_05, IA_APPLICATION_JSON, strlen(payload), &payload);
    cJSON_Delete(device_data);
    free(di);
    return;

end:
    wa_set_response_status(req_env, INTERNAL_SERVER_ERROR_5_00);
    if (di)
        free(di);
    return;
}

void put_value(int value, s7_dt_t t, char *out_buf)
{
    if (t == T_Bits)
    {
        *out_buf = (value != 0);
        return;
    }

    if (get_data_type_size(t) == 1)
    {
        *out_buf = value;
        return;
    }
    else if (get_data_type_size(t) == 2)
    {
        *(unsigned short *)out_buf = htons(value);
        return;
    }
    else if (get_data_type_size(t) == 4)
    {
        *(unsigned int *)out_buf = htonl(value);
        return;
    }
}

void res_s7_cmd_put_handler(restful_request_t *request, REQ_ENV_HANDLE req_env)
{
    int res = 0;
    int code = BAD_REQUEST_4_00;
    if (request->query == NULL || request->payload_len == 0)
    {
        wa_set_response_status(req_env, code);
        return;
    }
    char *payload = request->payload;
    int AREA, area_Number, start_address;
    int len;
    int items = 1;
    int t;
    s7_device_t *device;
    cJSON *rootvalues;
    char write_buf[128];
    char *write_buf_ptr = write_buf;
    char *malloc_buf = NULL;

    const char *di = parse_s7_cmd_query(request->query, &AREA, &area_Number, &start_address, NULL, &t);
    s7_dt_t type = (s7_dt_t)t;
    if (di == NULL)
        goto end;

    device = find_device_open(di);
    if (device == NULL)
    {
        goto end;
    }

    if (request->payload_fmt == IA_TEXT_PLAIN)
    {
        items = 1;
        if (type == T_Float)
        {
            float value2 = atof(payload);
            memcpy(write_buf_ptr, &value2, sizeof(float));
            len = sizeof(float);
        }
        else
        {
            int value2 = atoi(payload);
            put_value(value2, type, write_buf_ptr);
            len = get_data_type_size(type);
        }
    }
    else if (request->payload_fmt == IA_APPLICATION_JSON)
    {
        rootvalues = cJSON_Parse((const char *)payload);
        if (!cJSON_IsArray(rootvalues))
            goto end;
        items = cJSON_GetArraySize(rootvalues);
        len = get_data_type_size(type) * items;
        if (len > sizeof(write_buf))
        {
            malloc_buf = (char *)malloc(len);
            if (malloc_buf == NULL)
                goto end;
            write_buf_ptr = malloc_buf;
        }
        cJSON *item;
        char *write_ptr = write_buf_ptr;
        cJSON_ArrayForEach(item, rootvalues)
        {
            if (!cJSON_IsNumber(item))
                goto end;
            if (type == T_Float)
            {
                float value2 = item->valuedouble;
                memcpy(write_ptr, &value2, sizeof(float));
                write_ptr += sizeof(float);
            }
            else
            {
                int value2 = item->valueint;
                put_value(value2, type, write_ptr);
                write_ptr += get_data_type_size(type);
            }
        }
    }
    else
    {
        goto end;
    }

    // write Anaout\Anain\direct I\O,support of types:bytes,words,dwords(double words),s16int(16bits),s32int(32bits),float
    if (AREA == daveAnaOut || AREA == daveAnaIn || AREA == daveP)
    {
        if (type == T_Bits)
            goto end;
        res = daveWriteBytes(device->dc, AREA, area_Number, start_address, len, write_buf_ptr);
    }
    // write daveFlags,support of types:words,dwords,float
    else if (AREA == daveFlags)
    {
        if (type == T_Words || type == T_DWords || type == T_Float)
        {
            res = daveWriteBytes(device->dc, AREA, area_Number, start_address, len, write_buf_ptr);
        }
        else
        {
            fprintf(stderr, "[s7-plugin]No this type!\n");
            res = -1;
        }
    }
    // wirte outputs\inputs\data block,support of types:bits,bytes,words,dwords(double words),s16int(16bits),s32int(32bits),float
    else if (AREA == daveDB || AREA == daveInputs || AREA == daveOutputs)
    {
        if (type == T_Bits)
        {
            res = daveWriteBits(device->dc, AREA, area_Number, start_address, len, write_buf_ptr);
        }
        else
        {
            res = daveWriteBytes(device->dc, AREA, area_Number, start_address, len, write_buf_ptr);
        }
    }
    else // other area
    {
        fprintf(stderr, "[s7-plugin]No this area!\n");
        res = -1;
    }

    if (res == 0)
    {
        code = CHANGED_2_04;
    }
    else
    {
        printf("[s7-plugin]function result:%d=%s length:%d\n", res, daveStrerror(res), device->dc->AnswLen);
        code = INTERNAL_SERVER_ERROR_5_00;
    }
end:
    if (malloc_buf != NULL)
        free(malloc_buf);
    if (rootvalues != NULL)
        cJSON_Delete(rootvalues);

    wa_set_response_status(req_env, code);
    return;
}
